以飞机大战为例讲解Qt Scene的基本使用原理

Let’s make a GAME!!!!

本文将以Qt设计的一个飞机大战为例,讲解小游戏开发的一些基本要素。

基于Qt Scene的场景构建

场景、视窗和对象

QGraphicsScene

这个类就像一个场景类,你可以向场景类中添加各种各样的对象,例如放入一只猫或者一个英雄等等

QGraphicsView

这个类用于将场景进行可视化,如果不可视化场景,我们便看不到任何东西

QGraphicsItem

功能

这个类就是我们可以向场景中添加的对象,例如一个矩形(QGraphicsRectItem)

Item的组合

每个item可以有自己的parent,从而我们可以将多个item进行组合,这对于创造复杂的对象很有帮助,例如如果我们想创建一个坦克,坦克包含如下部分:

  • 炮管
  • 车身

我们可以令车身为炮管的parent,当parent被销毁时,child也会被销毁;而当chlid被销毁时,parent不会被销毁,这样我们可以实现如下的逻辑:当车身被打坏时,炮管连带也被销毁,而当炮管被打坏时,车身还在。

构建一个基本场景

综合代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsView>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

// create a scene
QGraphicsScene *scene = new QGraphicsScene();

// create an item
QGraphicsRectItem *rect = new QGraphicsRectItem();
rect->setRect(0,0,100,100); //0,0代表在view中的位置

// add the item to the scene
scene->addItem(rect); //在场景中添加item

// add a view, Without a view, nothing can be seen
QGraphicsView* view = new QGraphicsView(scene);
view->show(); //显示

return a.exec();
}

三者关系

三者关系可以用下图进行表示

graph TB
    subgraph QGraphicsScene

        subgraph QGraphicsView
            object1[QGraphicsRectItem]
        end

    end

视觉效果从上图可以看出,QGraphicsView其实就是可见区域,需要注意的是,当存在多个Item时,View默认是以多个Item的中心作为中心的,我们可以设置以某一个具体的Item作为中心或者以整个Scene作为中心。

坐标系系统

在Qt中,有三个坐标系系统需要我们注意:Scene、View和Item,左上角是三个坐标系的原点,通过改变其位置,我们能够得到不同的视觉效果。

对场景类的修改

尺寸修改

我们可以设置场景类的一些关键参数,从而实现理想的场景效果,例如在QGraphcsScene中,如果我们不限定其大小,随着场景中item越来越多,那么我们的Scene尺寸会越来越大,一般来说,我们会将Scene的尺寸和view的尺寸设置为一致。

1
2
3
4
view->setFixedSize(800,600);
scene->setSceneRect(0,0,800,600);
player->setPos(view->width()/2-player->rect().width()/2,view->height() - player->rect().height()); //设置玩家相对与view的位置
view->show();

对Item类的修改

添加item

1
2
3
4
5
case Qt::Key_Space:    //按下空格键
Bullet* bullet = new Bullet(); //创建一个新item
bullet->setPos(x(),y()); //设置item的位置
scene()->addItem(bullet); //添加item至场景中
break;

删除item

1
2
3
4
if(pos().y() < 0 - this->rect().height()){
scene()->removeItem(this); //当子弹跑出场景外时,从场景中移除对象
delete this;
}

音频和图像

音频及图像Resources管理

一般来说,我们将多媒体素材放置在Qt的resourse中,右键项目,Add New,在Qt一栏中选择resource,即可添加,其后缀为.qrc。点击add prefix可以添加前缀,就是划分文件夹以便分类保管不同的资源。

声音

配置工程文件

为了添加声音,我们需要使用多媒体,修改项目工程文件,在Qt组件中添加多媒体

1
2
QT       += core gui \
multimedia

添加背景音乐

1
2
3
QMediaPlayer* bgm = new QMediaPlayer();
bgm->setMedia(QUrl("qrc:/sound/fire.wav"));
bgm->play();

添加开火音效

在添加开火音效时,我们要注意音效的重复播放问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Player::fireSound()
{
/*
** Play fire sound effect, if the sound effect is playing
** we set the sound track to the beginning to make a continue
** firing sound effect, and if the sound effect is finished
** we replay it
*/
if(bullet_sound->state() == QMediaPlayer::PlayingState){
bullet_sound->setPosition(0);
}
else if(bullet_sound->state() == QMediaPlayer::StoppedState){
bullet_sound->play();
}
}

图像

背景设置

1
2
3
scene  = new QGraphicsScene();
scene->setSceneRect(posX,posY,width,height);
scene->setBackgroundBrush(QBrush(":/images/bg.png"));

游戏角色设置

QGraphicsPixmapItem

为了使用具有图片属性的对象,我们需要使用QGraphicsPixmapItem类,该类能够为对象添加图元

创建一个具有图元属性的对象并进行处理
1
2
3
4
5
6
7
8
9
10
11
/*
** Player image set
** setPixmap -- set the image of the player
** setTransformOriginPoint -- set the origin of the player
** setRotation -- rotate the player around origin
*/
player->setPixmap(QPixmap(":/images/player.png"));
//player->setRect(0,0,100,100);
player->setTransformOriginPoint(player->boundingRect().width()/2,
player->boundingRect().height()/2);
player->setRotation(180);

人机交互事件

人机交互有多种事件,例如鼠标点击,键盘点击等等,本文将使用键盘触发相应事件

keyPressEvent() 和 QKeyEvent

我们定义好了一个对象叫MyRect,继承自QGraphicsRectItem,同时定义了keyPressEvent()函数如下

1
2
3
4
void MyRect::keyPressEvent(QKeyEvent *event)
{
qDebug() << "MyRect knows that you pressed a key";
}

现在我们按下键盘,发现并没有事情发生,因为在Qt中,同一时刻只能有一个focus的对象响应键盘事件,因此我们要将我们的MyRect对象设置为focus的。

1
2
rect->setFlag(QGraphicsItem::ItemIsFocusable);   //设置为可Focus的
rect->setFocus(); //Focus rect,使其能够响应键盘事件

event 传递系统

对于一个事件,我们要知道以下几点:

  • 事件从何而来
  • 事件到哪里去
  • 事件何时触发

我们需要Qt中的event propogation system来控制事件的传递。具体原理如下

  1. QGraphicsView 监听键盘事件
  2. 当键盘事件来临后,QGraphicsView通知QGraphicsScene
  3. QGraphicsScene查看当前被Focus的QGraphicsItem,控制其动作
  4. QGraphicsItem检查并调用其keyPressEvent函数,完成动作

Timer

一些情况下我们需要触发定时器中断,此时我们需要设置Timer,例如在游戏中我们需要子弹自己运动,那么设置好定时器,然后将定时器与运动函数进行connect,从而实现子弹的自动移动

1
2
3
4
5
6
7
8
9
Bullet::Bullet()
{
// draw the rect
setRect(0,0,10,50);

QTimer* timer = new QTimer();
connect(timer, SIGNAL(timeout()), this, SLOT(move()));
timer->start(50);
}

游戏机制

碰撞检测

在子弹的飞行过程中,我们需要对子弹碰撞的物体进行检测,如果发现子弹碰撞到了飞机,那么立刻将子弹和飞机都销毁,

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
QList<QGraphicsItem*> colliding_item = collidingItems();  //获取碰撞的物体并保存在列表中
for(int i=0;i < colliding_item.size();i++){
if(typeid (*(colliding_item[i])) == typeid (Enemy)){ //如果碰撞的物体类型为 Enemy
//从场景中移除
scene() -> removeItem(colliding_item[i]); //remove the enemy
scene() -> removeItem(this);

//释放内存
delete colliding_item[i];
delete this;
return;
}
}

参考文献

0%